#include "StdAfx.h"
#include "RopeRenderNode.h"
#include "VisAreas.h"
#include "ObjMan.h"

#pragma warning(disable: 4244)

class TubeSurface : public _i_reference_target_t
{

public:
	Matrix34 m_worldTM;

	// If rkUpVector is not the zero vector, it will be used as 'up' in the frame calculations.  If it is the zero
	// vector, the Frenet frame will be used.  If bWantColors is 'true',
	// the vertex colors are allocated and set to black.  The application
	// needs to assign colors as needed.  If either of pkTextureMin or
	// pkTextureMax is not null, both must be not null.  In this case,
	// texture coordinates are generated for the surface.
	void GenerateSurface( spline::CatmullRomSpline<Vec3> *pSpline, float fRadius, bool bClosed,
		const Vec3& rkUpVector, int iMedialSamples, int iSliceSamples,
		bool bWantNormals, bool bWantColors, bool bSampleByArcLength,
		bool bInsideView, const Vec2* pkTextureMin,const Vec2* pkTextureMax);

	TubeSurface();
	~TubeSurface ();

	// member access
	Vec3& UpVector ();
	const Vec3& GetUpVector () const;
	int GetSliceSamples () const;

	// Generate vertices for the end slices.  These are useful when you build
	// an open tube and want to attach meshes at the ends to close the tube.
	// The input array must have size (at least) S+1 where S is the number
	// returned by GetSliceSamples.  Function GetTMinSlice is used to access
	// the slice corresponding to the medial curve evaluated at its domain
	// minimum, tmin.  Function GetTMaxSlice accesses the slice for the
	// domain maximum, tmax.  If the curve is closed, the slices are the same.
	void GetTMinSlice (Vec3* akSlice);
	void GetTMaxSlice (Vec3* akSlice);

	// If the medial curve is modified, for example if it is control point
	// based and the control points are modified, then you should call this
	// update function to recompute the tube surface geometry.
	void UpdateSurface ();

//////////////////////////////////////////////////////////////////////////
	// tessellation support
	int Index (int iS, int iM) { return iS + (m_iSliceSamples+1)*iM; };
	void ComputeSinCos ();
	void ComputeVertices (Vec3* akVertex);
	void ComputeNormals();
	void ComputeTextures (const Vec2& rkTextureMin,
		const Vec2& rkTextureMax, Vec2* akTexture);
	void ComputeConnectivity (int& riTQuantity, uint16*& raiConnect,bool bInsideView);

	float m_fRadius;
	int m_iMedialSamples, m_iSliceSamples;
	Vec3 m_kUpVector;
	float* m_afSin;
	float* m_afCos;
	bool m_bClosed, m_bSampleByArcLength;
	bool m_bCap;

	int nAllocVerts;
	int nAllocIndices;
	int nAllocSinCos;

	int iVQuantity;
	int iNumIndices;
	
	int iTQuantity; // Num triangles (use iNumIndices instead).

	Vec3* m_akTangents;
	Vec3* m_akBinormals;

	Vec3* m_akVertex;
	Vec3* m_akNormals;
	Vec2* m_akTexture;
	uint16 *m_pIndices;
	spline::CatmullRomSpline<Vec3> *m_pSpline;
};

//////////////////////////////////////////////////////////////////////////
TubeSurface::TubeSurface()
{
	m_afSin = NULL;
	m_afCos = NULL;

	m_bCap = false;
	m_bClosed = false;
	m_bSampleByArcLength = false;
	m_fRadius = 0;
	m_iMedialSamples = 0;
	m_iSliceSamples = 0;

	nAllocVerts = 0;
	nAllocIndices = 0;
	nAllocSinCos = 0;

	iVQuantity = 0;
	iTQuantity = 0;
	iNumIndices = 0;

	m_pSpline = NULL;
	m_akTangents = NULL;
	m_akBinormals = NULL;

	m_akVertex = NULL;
	m_akNormals = NULL;
	m_akTexture = NULL;
	m_pIndices = NULL;
}

//----------------------------------------------------------------------------
void TubeSurface::GenerateSurface( spline::CatmullRomSpline<Vec3> *pSpline,float fRadius,
													bool bClosed, const Vec3& rkUpVector, int iMedialSamples,
													int iSliceSamples, bool bWantNormals, bool bWantColors,
													bool bSampleByArcLength, bool bInsideView, const Vec2* pkTextureMin,const Vec2* pkTextureMax)
{
	assert((pkTextureMin && pkTextureMax)||(!pkTextureMin && !pkTextureMax));

	m_pSpline = pSpline;
	m_fRadius = fRadius;
	m_kUpVector = rkUpVector;
	m_iMedialSamples = iMedialSamples;
	m_iSliceSamples = iSliceSamples;
	m_bClosed = bClosed;
	m_bSampleByArcLength = bSampleByArcLength;

	m_bCap = !m_bClosed;
	// compute the surface vertices
	if ( m_bClosed )
		iVQuantity = (m_iSliceSamples+1)*(m_iMedialSamples+1);
	else
		iVQuantity = (m_iSliceSamples+1)*m_iMedialSamples;

	bool bAllocVerts = false;
	if (iVQuantity > nAllocVerts || !m_akVertex)
	{
		bAllocVerts = true;
		nAllocVerts = iVQuantity;

		delete [] m_akVertex;
		m_akVertex = new Vec3[nAllocVerts];
	}

	ComputeSinCos();
	ComputeVertices(m_akVertex);

	// compute the surface normals
	if ( bWantNormals )
	{
		if (bAllocVerts)
		{
			delete [] m_akNormals;
			delete [] m_akTangents;
			delete [] m_akBinormals;

			m_akNormals = new Vec3[iVQuantity];
			m_akTangents = new Vec3[iVQuantity];
			m_akBinormals = new Vec3[iVQuantity];
		}
		ComputeNormals();
	}

	// compute the surface textures coordinates
	if ( pkTextureMin && pkTextureMax )
	{
		if (bAllocVerts)
		{
			delete [] m_akTexture;
			m_akTexture = new Vec2[iVQuantity];
		}

		ComputeTextures(*pkTextureMin,*pkTextureMax,m_akTexture);
	}

	// compute the surface triangle connectivity
	ComputeConnectivity(iTQuantity,m_pIndices,bInsideView);

	// create the triangle mesh for the tube surface
	//Reconstruct(iVQuantity,akVertex,akNormal,akColor,akTexture,iTQuantity,aiConnect);
}

//----------------------------------------------------------------------------
TubeSurface::~TubeSurface ()
{
	delete[] m_pIndices;
	delete[] m_akTexture;
	delete[] m_akBinormals;
	delete[] m_akTangents;
	delete[] m_akNormals;
	delete[] m_akVertex;
	delete[] m_afSin;
	delete[] m_afCos;
}
//----------------------------------------------------------------------------
void TubeSurface::ComputeSinCos ()
{
	// Compute slice vertex coefficients.  The first and last coefficients
	// are duplicated to allow a closed cross section that has two different
	// pairs of texture coordinates at the shared vertex.

	if (m_iSliceSamples+1 != nAllocSinCos)
	{
		nAllocSinCos = m_iSliceSamples+1;
		delete []m_afSin;
		delete []m_afCos;
		m_afSin = new float[nAllocSinCos];
		m_afCos = new float[nAllocSinCos];
	}

	float fInvSliceSamples = 1.0f/(float)m_iSliceSamples;
	for (int i = 0; i < m_iSliceSamples; i++)
	{
		float fAngle = gf_PI2*fInvSliceSamples*i;
		m_afCos[i] = cosf(fAngle);
		m_afSin[i] = sinf(fAngle);
	}
	m_afSin[m_iSliceSamples] = m_afSin[0];
	m_afCos[m_iSliceSamples] = m_afCos[0];
}
//----------------------------------------------------------------------------
void TubeSurface::ComputeVertices (Vec3* akVertex)
{
	float fTMin = m_pSpline->GetRangeStart();
	float fTRange = m_pSpline->GetRangeEnd() - fTMin;

	// sampling by arc length requires the total length of the curve
/*	float fTotalLength;
	if ( m_bSampleByArcLength )
		//fTotalLength = m_pkMedial->GetTotalLength();
		fTotalLength = 0.0f;
	else
		fTotalLength = 0.0f;
*/
	if (Cry3DEngineBase::GetCVars()->e_Ropes == 2)
	{
		for (int i = 0, npoints = m_pSpline->num_keys()-1; i < npoints; i++)
		{
			gEnv->pRenderer->GetIRenderAuxGeom()->DrawLine( m_worldTM*m_pSpline->value(i),ColorB(0,255,0,255),m_worldTM*m_pSpline->value(i+1),ColorB(0,255,0,255),6 );
		}
	}

	// vertex construction requires a normalized time (uses a division)
	float fDenom;
	if ( m_bClosed )
		fDenom = 1.0f/(float)m_iMedialSamples;
	else
		fDenom = 1.0f/(float)(m_iMedialSamples-1);

	Vec3 kT_Prev=Vec3(1.0f,0,0);
	for (int iM = 0, iV = 0; iM < m_iMedialSamples; iM++)
	{
		float fT;
		if ( m_bSampleByArcLength )
			//fT = m_pSpline->GetTime(iM*fTotalLength*fDenom);
			fT = fTMin + iM*fTRange*fDenom;
		else
			fT = fTMin + iM*fTRange*fDenom;

		float fRadius = m_fRadius;

		// compute frame (position P, tangent T, normal N, binormal B)
		Vec3 kP, kP1, kT, kN, kB;

		{
			// Always use 'up' vector N rather than curve normal.  You must
			// constrain the curve so that T and N are never parallel.  To
			// build the frame from this, let
			//     B = Cross(T,N)/Length(Cross(T,N))
			// and replace
			//     N = Cross(B,T)/Length(Cross(B,T)).
			
			//kP = m_pkMedial->GetPosition(fT);
			//kT = m_pkMedial->GetTangent(fT);
			if (iM < m_iMedialSamples-1)
			{
				m_pSpline->interpolate( fT,kP );
				m_pSpline->interpolate( fT+0.01f,kP1 );
			}
			else
			{
				m_pSpline->interpolate( fT-0.01f,kP );
				m_pSpline->interpolate( fT,kP1 );
			}

			kT = (kP1-kP);
			if (!kT.IsZero())
				kT.NormalizeFast();
			else
			{
				// Take direction of last points.
				kT = kT_Prev;
			}
			kT_Prev = kT;
			//kT = m_pkMedial->GetTangent(fT);

			kB = kT.Cross(m_kUpVector);
			if (kB.IsZero())
				kB = Vec3(1.0f,0,0);
			kB.NormalizeFast();
			kN = kB.Cross(kT);
			kN.NormalizeFast();
		}


		// compute slice vertices, duplication at end point as noted earlier
		int iSave = iV;
		for (int i = 0; i < m_iSliceSamples; i++)
		{
			akVertex[iV] = kP + fRadius*(m_afCos[i]*kN + m_afSin[i]*kB);

			if (Cry3DEngineBase::GetCVars()->e_Ropes == 2)
			{
				ColorB col = ColorB(255,0,0,255);
				if (i == 0)
					col = ColorB(255,0,0,255);
				else if (i == 1)
					col = ColorB(0,255,0,255);
				else
					col = ColorB(0,0,255,255);
				gEnv->pRenderer->GetIRenderAuxGeom()->DrawPoint( m_worldTM*akVertex[iV],col,8 );
			}
			
			iV++;
		}
		akVertex[iV] = akVertex[iSave];
		iV++;
	}

	if ( m_bClosed )
	{
		for (int i = 0; i <= m_iSliceSamples; i++)
		{
			int i1 = Index(i,m_iMedialSamples);
			int i0 = Index(i,0);
			akVertex[i1] = akVertex[i0];
		}
	}
}
//----------------------------------------------------------------------------
void TubeSurface::ComputeNormals ()
{
	int iS, iSm, iSp, iM, iMm, iMp;
	Vec3 kDir0, kDir1;

	// interior normals (central differences)
	for (iM = 1; iM <= m_iMedialSamples-2; iM++)
	{
		for (iS = 0; iS < m_iSliceSamples; iS++)
		{
			iSm = ( iS > 0 ? iS-1 : m_iSliceSamples-1 );
			iSp = iS + 1;
			iMm = iM - 1;
			iMp = iM + 1;
			kDir0 = m_akVertex[Index(iSm,iM)] - m_akVertex[Index(iSp,iM)];
			kDir1 = m_akVertex[Index(iS,iMm)] - m_akVertex[Index(iS,iMp)];

			if (kDir0.IsZero(1e-10f))
				kDir0 = Vec3(1,0,0);
			if (kDir0.IsZero(1e-10f))
				kDir1 = Vec3(0,1,0);
			
			Vec3 kN = kDir0.Cross(kDir1);          // Normal.
			if (kN.IsZero(1e-10f))
				kN = Vec3(0,0,1);
			kN.NormalizeFast();
			Vec3 kT = kDir0; kT.NormalizeFast();              // Tangent
			Vec3 kB = kDir1; kB.NormalizeFast();              // Binormal.
			int nIndex = Index(iS,iM);
			m_akNormals[nIndex] = kN;
			m_akTangents[nIndex] = kT;
			m_akBinormals[nIndex] = kB;
		}
		m_akNormals[Index(m_iSliceSamples,iM)] = m_akNormals[Index(0,iM)];
		m_akTangents[Index(m_iSliceSamples,iM)] = m_akTangents[Index(0,iM)];
		m_akBinormals[Index(m_iSliceSamples,iM)] = m_akBinormals[Index(0,iM)];
	}

	// boundary normals
	if ( m_bClosed )
	{
		// central differences
		for (iS = 0; iS < m_iSliceSamples; iS++)
		{
			iSm = ( iS > 0 ? iS-1 : m_iSliceSamples-1 );
			iSp = iS + 1;

			// m = 0
			kDir0 = m_akVertex[Index(iSm,0)] - m_akVertex[Index(iSp,0)];
			kDir1 = m_akVertex[Index(iS,m_iMedialSamples-1)] - m_akVertex[Index(iS,1)];
			m_akNormals[iS] = kDir0.Cross(kDir1).GetNormalized();

			// m = max
			m_akNormals[Index(iS,m_iMedialSamples)] = m_akNormals[Index(iS,0)];
		}
		m_akNormals[Index(m_iSliceSamples,0)] = m_akNormals[Index(0,0)];
		m_akNormals[Index(m_iSliceSamples,m_iMedialSamples)] = m_akNormals[Index(0,m_iMedialSamples)];
	}
	else
	{
		// one-sided finite differences

		// m = 0
		for (iS = 0; iS < m_iSliceSamples; iS++)
		{
			iSm = ( iS > 0 ? iS-1 : m_iSliceSamples-1 );
			iSp = iS + 1;
			kDir0 = m_akVertex[Index(iSm,0)] - m_akVertex[Index(iSp,0)];
			kDir1 = m_akVertex[Index(iS,0)] - m_akVertex[Index(iS,1)];
			//m_akNormals[Index(iS,0)] = kDir0.Cross(kDir1).GetNormalized();

			if (kDir0.IsZero())
				kDir0 = Vec3(1,0,0);
			if (kDir1.IsZero())
				kDir1 = Vec3(0,1,0);

			Vec3 kN = kDir0.Cross(kDir1); kN.NormalizeFast(); // Normal.
			if (kN.IsZero())
				kN = Vec3(0,0,1);
			Vec3 kT = kDir0; kT.NormalizeFast();              // Tangent
			Vec3 kB = kDir1; kB.NormalizeFast();              // Binormal.
			int nIndex = iS;
			m_akNormals[nIndex] = kN;
			m_akTangents[nIndex] = kT;
			m_akBinormals[nIndex] = kB;
		}
		m_akNormals[Index(m_iSliceSamples,0)] = m_akNormals[Index(0,0)];
		m_akTangents[Index(m_iSliceSamples,0)] = m_akTangents[Index(0,0)];
		m_akBinormals[Index(m_iSliceSamples,0)] = m_akBinormals[Index(0,0)];

		// m = max-1
		for (iS = 0; iS < m_iSliceSamples; iS++)
		{
			iSm = ( iS > 0 ? iS-1 : m_iSliceSamples-1 );
			iSp = iS + 1;
			kDir0 = m_akVertex[Index(iSm,m_iMedialSamples-1)] -	m_akVertex[Index(iSp,m_iMedialSamples-1)];
			kDir1 = m_akVertex[Index(iS,m_iMedialSamples-2)] - m_akVertex[Index(iS,m_iMedialSamples-1)];
			//m_akNormals[iS] = kDir0.Cross(kDir1).GetNormalized();

			if (kDir0.IsZero())
				kDir0 = Vec3(1,0,0);
			if (kDir1.IsZero())
				kDir1 = Vec3(0,1,0);
			
			Vec3 kN = kDir0.Cross(kDir1);                     // Normal
			if (kN.IsZero())
				kN = Vec3(0,0,1);
			kN.NormalizeFast();
			Vec3 kT = kDir0; kT.NormalizeFast();              // Tangent
			Vec3 kB = kDir1; kB.NormalizeFast();              // Binormal.
			int nIndex = Index(iS,m_iMedialSamples-1);
			m_akNormals[nIndex] = kN;
			m_akTangents[nIndex] = kT;
			m_akBinormals[nIndex] = kB;
		}
		m_akNormals[Index(m_iSliceSamples,m_iMedialSamples-1)] = m_akNormals[Index(0,m_iMedialSamples-1)];
		m_akTangents[Index(m_iSliceSamples,m_iMedialSamples-1)] = m_akTangents[Index(0,m_iMedialSamples-1)];
		m_akBinormals[Index(m_iSliceSamples,m_iMedialSamples-1)] = m_akBinormals[Index(0,m_iMedialSamples-1)];
	}
}

//----------------------------------------------------------------------------
void TubeSurface::ComputeTextures (const Vec2& rkTextureMin,
																	 const Vec2& rkTextureMax, Vec2* akTexture)
{
	Vec2 kTextureRange = rkTextureMax - rkTextureMin;
	int iMMax = ( m_bClosed ? m_iMedialSamples : m_iMedialSamples - 1 );
	for (int iM = 0, iV = 0; iM <= iMMax; iM++)
	{
		float fMRatio = ((float)iM)/((float)iMMax);
		float fMValue = rkTextureMin.y + fMRatio*kTextureRange.y;
		for (int iS = 0; iS <= m_iSliceSamples; iS++)
		{
			float fSRatio = ((float)iS)/((float)m_iSliceSamples);
			float fSValue = rkTextureMin.x + fSRatio*kTextureRange.x;
			akTexture[iV].x = fSValue;
			akTexture[iV].y = fMValue;
			iV++;
		}
	}
}
//----------------------------------------------------------------------------
void TubeSurface::ComputeConnectivity (int& riTQuantity, uint16*& raiConnect, bool bInsideView)
{
	if ( m_bClosed )
		riTQuantity = 2*m_iSliceSamples*m_iMedialSamples;
	else
		riTQuantity = 2*m_iSliceSamples*(m_iMedialSamples-1);

	iNumIndices = riTQuantity*3;
	
	if (m_bCap)
	{
		riTQuantity += ((m_iSliceSamples-2)*3) * 2;
		iNumIndices += ((m_iSliceSamples-2)*3) * 2;
	}

	if (nAllocIndices != iNumIndices)
	{
		if (raiConnect)
			delete []raiConnect;
		nAllocIndices = iNumIndices;
		raiConnect = new uint16[nAllocIndices];
	}

	int iM, iMStart, i0, i1, i2, i3, i;
	uint16* aiConnect = raiConnect;
	for (iM = 0, iMStart = 0; iM < m_iMedialSamples-1; iM++)
	{
		i0 = iMStart;
		i1 = i0 + 1;
		iMStart += m_iSliceSamples + 1;
		i2 = iMStart;
		i3 = i2 + 1;
		for (i = 0; i < m_iSliceSamples; i++, aiConnect += 6)
		{
			if ( bInsideView )
			{
				aiConnect[0] = i0++;
				aiConnect[1] = i2;
				aiConnect[2] = i1;
				aiConnect[3] = i1++;
				aiConnect[4] = i2++;
				aiConnect[5] = i3++;
			}
			else  // outside view
			{
				aiConnect[0] = i0++;
				aiConnect[1] = i1;
				aiConnect[2] = i2;
				aiConnect[3] = i1++;
				aiConnect[4] = i3++;
				aiConnect[5] = i2++;

				if (Cry3DEngineBase::GetCVars()->e_Ropes == 2)
				{
					ColorB col = ColorB(100,100,0,50);
					gEnv->pRenderer->GetIRenderAuxGeom()->DrawTriangle( m_worldTM*m_akVertex[aiConnect[0]],col,m_worldTM*m_akVertex[aiConnect[1]],col,m_worldTM*m_akVertex[aiConnect[2]],col );
					col = ColorB(100,0,100,50);
					gEnv->pRenderer->GetIRenderAuxGeom()->DrawTriangle( m_worldTM*m_akVertex[aiConnect[3]],col,m_worldTM*m_akVertex[aiConnect[4]],col,m_worldTM*m_akVertex[aiConnect[5]],col );
				}
			}
		}
	}

	if ( m_bClosed )
	{
		i0 = iMStart;
		i1 = i0 + 1;
		i2 = 0;
		i3 = i2 + 1;
		for (i = 0; i < m_iSliceSamples; i++, aiConnect += 6)
		{
			if ( bInsideView )
			{
				aiConnect[0] = i0++;
				aiConnect[1] = i2;
				aiConnect[2] = i1;
				aiConnect[3] = i1++;
				aiConnect[4] = i2++;
				aiConnect[5] = i3++;
			}
			else  // outside view
			{
				aiConnect[0] = i0++;
				aiConnect[1] = i1;
				aiConnect[2] = i2;
				aiConnect[3] = i1++;
				aiConnect[4] = i3++;
				aiConnect[5] = i2++;
			}
		}
	}
	else if (m_bCap)
	{
		i0 = 0;
		i1 = 1;
		i2 = 2;
		// Begining Cap.
		for (i = 0; i < m_iSliceSamples-2; i++, aiConnect += 3)
		{
			aiConnect[0] = i0;
			aiConnect[1] = i2++;
			aiConnect[2] = i1++;
		}
		i0 = iMStart;
		i1 = i0 + 1;
		i2 = i0 + 2;
		// Ending Cap.
		for (i = 0; i < m_iSliceSamples-2; i++, aiConnect += 3)
		{
			aiConnect[0] = i0;
			aiConnect[1] = i1++;
			aiConnect[2] = i2++;
		}
	}
}

/*
//----------------------------------------------------------------------------
void TubeSurface::GetTMinSlice (Vec3* akSlice)
{
	for (int i = 0; i <= m_iSliceSamples; i++)
		akSlice[i] = m_akVertex[i];
}
//----------------------------------------------------------------------------
void TubeSurface::GetTMaxSlice (Vec3* akSlice)
{
	int j = m_iVertexQuantity - m_iSliceSamples - 1;
	for (int i = 0; i <= m_iSliceSamples; i++, j++)
		akSlice[i] = m_akVertex[j];
}
//----------------------------------------------------------------------------
void TubeSurface::UpdateSurface ()
{
	ComputeVertices(m_akVertex);
	if ( m_akNormal )
		ComputeNormals(m_akVertex,m_akNormal);
}
*/
//----------------------------------------------------------------------------

#if EMBED_PHYSICS_AS_FIBER
# define YIELD_PHYSFIBER() { JobManager::Fiber::SwitchFiberDirect(); } 
#else 
# define YIELD_PHYSFIBER() { Sleep(0); } 
#endif 

//////////////////////////////////////////////////////////////////////////
// Every Rope will keep a pointer to this and increase reference count.
//////////////////////////////////////////////////////////////////////////
class CRopeSurfaceCache : public _i_reference_target_t
{
public:
	static CRopeSurfaceCache* GetSurfaceCache();
	static void ReleaseSurfaceCache(CRopeSurfaceCache* cache);

	static void CleanupCaches();

public:
	struct GetLocalCache
	{
		GetLocalCache()
			: pCache(CRopeSurfaceCache::GetSurfaceCache())
		{}

		~GetLocalCache()
		{
			CRopeSurfaceCache::ReleaseSurfaceCache(pCache);
		}

		CRopeSurfaceCache* pCache;

	private:
		GetLocalCache(const GetLocalCache&);
		GetLocalCache& operator = (const GetLocalCache&);
	};

public:
	TubeSurface tubeSurface;

	CRopeSurfaceCache() {};
	~CRopeSurfaceCache() {}
	
private:
	static CryCriticalSectionNonRecursive g_CacheLock;
	static std::vector<CRopeSurfaceCache*> g_CachePool;
};

CryCriticalSectionNonRecursive CRopeSurfaceCache::g_CacheLock;
std::vector<CRopeSurfaceCache*> CRopeSurfaceCache::g_CachePool;

CRopeSurfaceCache* CRopeSurfaceCache::GetSurfaceCache()
{
	while (!g_CacheLock.TryLock())
		YIELD_PHYSFIBER();

	CRopeSurfaceCache* ret = NULL;

	if (g_CachePool.empty())
	{
		ret = new CRopeSurfaceCache;
	}
	else
	{
		ret = g_CachePool.back();
		g_CachePool.pop_back();
	}

	g_CacheLock.Unlock();
	return ret;
}

void CRopeSurfaceCache::ReleaseSurfaceCache(CRopeSurfaceCache* cache)
{
	while (!g_CacheLock.TryLock())
		YIELD_PHYSFIBER();

	g_CachePool.push_back(cache);
	g_CacheLock.Unlock();
}

void CRopeSurfaceCache::CleanupCaches()
{
	CryAutoLock<CryCriticalSectionNonRecursive> lock(g_CacheLock);

	for (std::vector<CRopeSurfaceCache*>::iterator it = g_CachePool.begin(), itEnd = g_CachePool.end(); it != itEnd; ++ it)
		delete *it;

	stl::free_container(g_CachePool);
}

//////////////////////////////////////////////////////////////////////////
CRopeRenderNode::CRopeRenderNode()
: m_pos( 0, 0, 0 )
, m_localBounds( Vec3( -1, -1, -1 ), Vec3( 1, 1, 1 ) )
, m_pRenderMesh(0)
, m_pMaterial(0)
, m_pPhysicalEntity(0)
, m_nLinkedEndsMask(0)
{	
	m_pMaterial = GetMatMan()->GetDefaultMaterial();

	m_sName.Format( "Rope_%X",this );

	memset( &m_params,0,sizeof(m_params) );

	m_bModified = true;
	m_bRopeCreatedInsideVisArea = false;

	m_worldTM.SetIdentity();
	m_InvWorldTM.SetIdentity();
	m_WSBBox.min = Vec3(0,0,0);
	m_WSBBox.max = Vec3(1,1,1);
	m_bNeedToReRegister = true;
	m_bStaticPhysics = false;
	m_nEntityOwnerId = 0;
}

//////////////////////////////////////////////////////////////////////////
CRopeRenderNode::~CRopeRenderNode()
{
	StopRopeSound();
	Dephysicalize();
	Get3DEngine()->UnRegisterEntity( this );
  Get3DEngine()->FreeRenderNodeState(this);
	SAFE_RELEASE(m_pRenderMesh);
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::StaticReset()
{
	CRopeSurfaceCache::CleanupCaches();
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::GetLocalBounds( AABB& bbox )
{ 
	bbox = m_localBounds; 
};

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::SetMatrix( const Matrix34& mat )
{
	m_worldTM = mat;
	m_InvWorldTM = m_worldTM.GetInverted();
	m_pos = mat.GetTranslation();

	m_WSBBox.SetTransformedAABB( mat,m_localBounds );

	Get3DEngine()->RegisterEntity( this );
	m_bNeedToReRegister = false;

	if (!m_pPhysicalEntity)
		Physicalize();
	else
	{
		// Just move physics.
		pe_params_pos par_pos;
		par_pos.pMtx3x4 = &m_worldTM;
		m_pPhysicalEntity->SetParams(&par_pos);

		IVisArea * pVisArea = GetEntityVisArea();
		if ((pVisArea!=0) != m_bRopeCreatedInsideVisArea)
		{
			// Rope moved between vis area and outside.
			m_bRopeCreatedInsideVisArea = pVisArea != 0;

			pe_params_flags par_flags;
			if (m_params.nFlags & eRope_CheckCollisinos)
			{
				// If we are inside vis/area disable terrain collisions.
				if (!m_bRopeCreatedInsideVisArea)
					par_flags.flagsOR = rope_collides_with_terrain;
				else
					par_flags.flagsAND = ~rope_collides_with_terrain;
			}
			m_pPhysicalEntity->SetParams(&par_flags);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
EERType CRopeRenderNode::GetRenderNodeType() 
{ 
	return eERType_Rope;
}

//////////////////////////////////////////////////////////////////////////
const char* CRopeRenderNode::GetEntityClassName() const
{ 
	return "Rope";
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::SetName( const char *sName ) 
{ 
	m_sName = sName;
}

//////////////////////////////////////////////////////////////////////////
const char* CRopeRenderNode::GetName() const 
{ 
	return m_sName;
}

//////////////////////////////////////////////////////////////////////////
Vec3 CRopeRenderNode::GetPos( bool bWorldOnly ) const 
{ 
	return m_pos;
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::Render( const SRendParams& rParams )
{
  FUNCTION_PROFILER_3DENGINE;

	if (GetCVars()->e_Ropes == 0 || (m_dwRndFlags & ERF_HIDDEN) || m_pMaterial == NULL)
		return; // false;

	if (GetCVars()->e_Ropes == 2)
	{
		UpdateRenderMesh();
		return; // true;
	}

	if (m_bModified)
		UpdateRenderMesh();

	if (!m_pRenderMesh || m_pRenderMesh->GetVerticesCount() <= 3)
		return; // false;

	IRenderer *pRend = GetRenderer();

	CRenderObject *pObj = pRend->EF_GetObject_Temp();
  if (!pObj)
    return; // false;
	pObj->m_pRenderNode = this;
  pObj->m_DissolveRef = rParams.nDissolveRef;
	pObj->m_fSort = rParams.fCustomSortOffset;
	pObj->m_ObjFlags |= FOB_TRANS_MASK | rParams.dwFObjFlags;
	pObj->m_fAlpha = rParams.fAlpha;	
	pObj->m_II.m_AmbColor = rParams.AmbientColor;
	pObj->m_II.m_Matrix = m_worldTM;

	pObj->m_bHasShadowCasters = false;
	if (GetCVars()->e_Shadows && rParams.m_ShadowMapCasters)
	{
		SRenderObjData * pOD = pRend->EF_GetObjData(pObj, true);
		if (pOD)
		{
			pOD->m_ShadowCasters = rParams.m_ShadowMapCasters;
			pObj->m_bHasShadowCasters = true;
		}
	}

	IVisArea *pVisArea = GetEntityVisArea();

	pObj->m_ObjFlags |= FOB_INSHADOW;

	//if (rParams.pShaderParams && rParams.pShaderParams->Num())
  //	pObj->m_ShaderParams = rParams.pShaderParams;


	////////////////////////////////////////////////////////////////////////////////////////////////////
	// Set render quality
	////////////////////////////////////////////////////////////////////////////////////////////////////
	pObj->m_nRenderQuality = (uint16)(rParams.fRenderQuality * 65535.0f);
	pObj->m_fDistance =	rParams.fDistance;

	pObj->m_DynLMMask[m_nRenderThreadListID] = rParams.nDLightMask;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// Add render elements
	////////////////////////////////////////////////////////////////////////////////////////////////////

	IMaterial *pMaterial = m_pMaterial;
	if (rParams.pMaterial)
		pMaterial = rParams.pMaterial;
	pObj->m_nMaterialLayers = m_nMaterialLayers;


	//////////////////////////////////////////////////////////////////////////
	if (GetCVars()->e_DebugDraw)
	{
		if (RenderDebugInfo(rParams))
			return; // true;
	}
	//////////////////////////////////////////////////////////////////////////

	m_pRenderMesh->Render(rParams, pObj, pMaterial);

//	return true;
}

//////////////////////////////////////////////////////////////////////////
bool CRopeRenderNode::RenderDebugInfo( const SRendParams &rParams )
{
	if(Get3DEngine()->IsRenderingIntoShadowMap())
		return false;

	IRenderer * pRend = GetRenderer();
	IRenderAuxGeom *pAuxGeom = GetRenderer()->GetIRenderAuxGeom();

//	bool bVerbose = GetCVars()->e_DebugDraw > 1;
	bool bOnlyBoxes = GetCVars()->e_DebugDraw < 0;
	
	if (bOnlyBoxes)
		return true;

	if (m_pRenderMesh)
	{
		SGeometryDebugDrawInfo dd;
		dd.tm = m_worldTM;
		m_pRenderMesh->DebugDraw( dd );
	}

	Vec3 pos = m_worldTM.GetTranslation();

	int nTris = m_pRenderMesh->GetIndicesCount()/3;
	// text
	float color[4] = {0,1,1,1};

	if(GetCVars()->e_DebugDraw==2)					// color coded polygon count
		pRend->DrawLabelEx( pos,1.3f,color,true,true,"%d",nTris );
	 else if(GetCVars()->e_DebugDraw==5)		// number of render materials (color coded)
		 pRend->DrawLabelEx( pos,1.3f,color,true,true,"1" );

	return true;
}

//////////////////////////////////////////////////////////////////////////
IPhysicalEntity* CRopeRenderNode::GetPhysics() const 
{ 
	return m_pPhysicalEntity;
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::SetPhysics( IPhysicalEntity* ) 
{
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::Physicalize( bool bInstant )
{
	if (m_params.mass <= 0)
		m_bStaticPhysics = true;
	else
		m_bStaticPhysics = false;

	if (!m_pPhysicalEntity || (m_pPhysicalEntity->GetType()==PE_ROPE)!=(m_params.mass>0))
	{
		if (m_pPhysicalEntity)
			gEnv->pPhysicalWorld->DestroyPhysicalEntity(m_pPhysicalEntity);
		m_pPhysicalEntity = gEnv->pPhysicalWorld->CreatePhysicalEntity( (m_bStaticPhysics) ? PE_STATIC : PE_ROPE,
			NULL,(IRenderNode*)this,PHYS_FOREIGN_ID_ROPE, m_nEntityOwnerId ? (m_nEntityOwnerId&0xFFFF) : -1);
		if (!m_pPhysicalEntity)
			return;
	}

	if (m_points.size() < 2 || m_params.nPhysSegments < 1)
		return;

	pe_params_pos par_pos;
	par_pos.pMtx3x4 = &m_worldTM;
	m_pPhysicalEntity->SetParams(&par_pos);

	int surfaceTypesId[MAX_SUB_MATERIALS];
	memset( surfaceTypesId,0,sizeof(surfaceTypesId) );
	surfaceTypesId[0] = 0;
	if (m_pMaterial)
		m_pMaterial->FillSurfaceTypeIds(surfaceTypesId);

	Vec3* pSrcPoints = &m_points[0];
	int nSrcPoints = (int)m_points.size();
	int nSrcSegments = nSrcPoints-1;

	m_physicsPoints.resize( m_params.nPhysSegments+1 );
	int nPoints = (int)m_physicsPoints.size();
	int nTargetSegments = nPoints-1;
	int i;

	//////////////////////////////////////////////////////////////////////////
	// Set physical ropes params.
	pe_params_rope pr;

	pr.nSegments = nTargetSegments;
	pr.pPoints = &m_physicsPoints[0];
	pr.length = 0;

	pr.pEntTiedTo[0] = 0;
	pr.pEntTiedTo[1] = 0;

	// Calc original length.
	for (i = 0; i < nSrcPoints-1; i++)
	{
		pr.length += (pSrcPoints[i+1] - pSrcPoints[i]).GetLength();
	}

	// Make rope segments of equal length.
	if (nSrcPoints==2 && m_params.tension<0)
	{
		int idir,ibound[2] = { 0,128 };
		double h,s,L,a,k,ra,seglen,rh,abound[2],x;
		Vec3 origin,axisx;
		static double g_atab[128];
		static int g_bTabFilled=0;

		h = fabs_tpl(pSrcPoints[0].z-pSrcPoints[1].z);
		s = (Vec2(pSrcPoints[0])-Vec2(pSrcPoints[1])).GetLength();
		L = (pSrcPoints[0]-pSrcPoints[1]).GetLength()*(1-m_params.tension);
		if (h<s*0.0001) {
			// will solve for 2*a*sinh(s/(2*a)) == L
			rh = L; k = 0;
		} else {
			// will solve for 2*a*sinh(L/(2*a)) == h/sinh(k)
			k = h/L;
			k = log_tpl((1+k)/(1-k))*0.5; // k = atanh(k)
			rh = h/sinh(k);
		}
		if (!g_bTabFilled)
			for(i=0,a=0.125,g_bTabFilled=1; i<128; i++,a+=(8.0/128)) 
				g_atab[i] = 2*a*sinh(1/(2*a));
		ibound[0]=0; ibound[1]=127;
		do {
			i = (ibound[0]+ibound[1])>>1;
			ibound[isneg(g_atab[i]*s-L)] = i;
		} while(ibound[1]>ibound[0]+1);
		abound[1] = (abound[0] = (ibound[0]*(8.0/128)+0.125)*s)*2;
		i=0; do {
			a = (abound[0]+abound[1])*0.5;
			x = 2*a*sinh(s/(2*a))-rh;
			abound[isneg(x)] = a;
		}	while(++i<16);
		ra = 1/(a = (abound[0]+abound[1])*0.5);

		idir = isneg(pSrcPoints[0].z-pSrcPoints[1].z);
		pr.pPoints[0] = pSrcPoints[0];
		pr.pPoints[nTargetSegments] = pSrcPoints[1];
		(axisx = (Vec2(pSrcPoints[idir^1])-Vec2(pSrcPoints[idir]))/s).z = 0;
		origin = (pSrcPoints[0]+pSrcPoints[1])*0.5;
		origin.z = pSrcPoints[idir].z;
		origin += axisx*(a*k); x = -s*0.5-a*k;
		origin.z -= a*cosh(x*ra);
		seglen = L/nTargetSegments;
		for(i=1;i<nTargetSegments;i++) {
			x = seglen*ra+sinh(x*ra);
			x = log(x+sqrt(x*x+1))*a; // x = asinh(x)*a;
			pr.pPoints[(nTargetSegments&-idir)+i-(i&-idir)*2] = origin + axisx*x + Vec3(0,0,1)*a*cosh(x*ra);
		}
	} else
	{
		int iter,ivtx;
		float ka,kb,kc,kd;
		float rnSegs = 1.0f/nTargetSegments;
		float len = pr.length;

		Vec3 dir,v0;
		
		iter=3; 
		do {
			pr.pPoints[0] = pSrcPoints[0];
			for(i=ivtx=0; i<nTargetSegments && ivtx<nSrcSegments; )
			{
				dir = pSrcPoints[ivtx+1]-pSrcPoints[ivtx];
				v0 = pSrcPoints[ivtx] - pr.pPoints[i];
				ka = dir.GetLengthSquared();
				kb = v0*dir;
				kc = v0.GetLengthSquared() - sqr(len*rnSegs);
				kd = sqrt_tpl(max(0.0f,kb*kb-ka*kc));
				if (kd-kb < ka)
					pr.pPoints[++i] = pSrcPoints[ivtx] + dir*((kd-kb)/ka);
				else
					++ivtx;
			}
			len *= (1.0f-(nTargetSegments-i)*rnSegs);
			len += (pSrcPoints[nSrcSegments] - pr.pPoints[i]).GetLength();
		} while(--iter);
		pr.pPoints[nTargetSegments] = pSrcPoints[nSrcSegments]; // copy last point
		dir = pr.pPoints[nTargetSegments] - pr.pPoints[i]; 
		if (i+1 < nTargetSegments) 
		{
			for(ivtx=i+1,rnSegs=1.0f/(nTargetSegments-i); ivtx<nTargetSegments; ivtx++)
			{
				pr.pPoints[ivtx] = pr.pPoints[i] + dir*((ivtx-i)*rnSegs);
			}
		}
		// Update length.
		pr.length = 0;
		for (i = 0; i < nPoints-1; i++)
		{
			pr.length += (pr.pPoints[i+1]-pr.pPoints[i]).GetLength();
		}
	}

	// Transform to world space.
	for (i = 0; i < nPoints; i++)
	{
		pr.pPoints[i] = m_worldTM.TransformPoint(pr.pPoints[i]);
	}

	if (!m_bStaticPhysics)
	{
		//////////////////////////////////////////////////////////////////////////
		pe_params_flags par_flags;
		par_flags.flags = pef_never_affect_triggers | pef_log_state_changes | pef_log_poststep;
		if (m_params.nFlags & eRope_Subdivide)
			par_flags.flags |= rope_subdivide_segs;
		if (m_params.nFlags & eRope_CheckCollisinos)
		{
			par_flags.flags |= rope_collides;
			if (m_params.nFlags & eRope_NoAttachmentCollisions)
				par_flags.flags |= rope_ignore_attachments;
			// If we are inside vis/area disable terrain collisions.
			if (!m_bRopeCreatedInsideVisArea)
			{
				par_flags.flags |= rope_collides_with_terrain;
			}
		}
		par_flags.flags |= rope_traceable;
		if (m_params.mass <= 0)
			par_flags.flags |= pef_disabled;
		m_pPhysicalEntity->SetParams(&par_flags);

		pr.mass = m_params.mass;
		pr.airResistance = m_params.airResistance;
		pr.collDist = m_params.fThickness;
		pr.sensorRadius = m_params.fAnchorRadius;
		pr.maxForce = m_params.maxForce;
		pr.jointLimit = m_params.jointLimit;
		pr.friction = m_params.friction;
		pr.frictionPull = m_params.frictionPull;
		pr.wind = m_params.wind;
		pr.windVariance = m_params.windVariance;
		pr.nMaxSubVtx = m_params.nMaxSubVtx;
		pr.surface_idx = surfaceTypesId[0];

		AnchorEndPoints(pr);

		if (m_params.tension != 0)
			pr.length *= 1-m_params.tension;
		else if (nSrcPoints==2)
			pr.length *= 0.999f;

		m_pPhysicalEntity->SetParams(&pr);
		//////////////////////////////////////////////////////////////////////////

		// After creation rope is put to sleep, will be awaked on first render after modify.
		pe_action_awake pa;
		pa.bAwake = 0;
		m_pPhysicalEntity->Action( &pa );

		pe_simulation_params simparams;
		simparams.maxTimeStep = m_params.maxTimeStep;
		simparams.damping = m_params.damping;
		simparams.minEnergy = m_params.minEnergy;
		simparams.gravity = m_params.gravity;
		m_pPhysicalEntity->SetParams( &simparams );

		m_bRopeCreatedInsideVisArea = GetEntityVisArea() != 0;
	} else
	{
		primitives::capsule caps;
		pe_params_pos pp;
		IGeomManager *pGeoman = gEnv->pPhysicalWorld->GetGeomManager();
		phys_geometry *pgeom;
		pe_geomparams gp;
		pp.pos = pr.pPoints[0];
		m_pPhysicalEntity->SetParams(&pp);
		caps.r = m_params.fThickness;

		/*
		m_pPhysicalEntity->RemoveGeometry()
		for(i=0; i<nPoints-1; i++) 
		{
			caps.axis = pr.pPoints[i+1]-pr.pPoints[i];
			caps.axis /= (caps.hh = caps.axis.len());	caps.hh *= 0.5f;
			caps.center = (pr.pPoints[i+1]+pr.pPoints[i])*0.5f-pr.pPoints[0];
			pgeom = pGeoman->RegisterGeometry(pGeoman->CreatePrimitive(primitives::capsule::type, &caps), surfaceTypesId[0]);
			pgeom->nRefCount = 0;	// we want it to be deleted together with the physical entity
			m_pPhysicalEntity->AddGeometry(pgeom, &gp);
		}
		*/
		pe_action_remove_all_parts removeall;
		m_pPhysicalEntity->Action( &removeall );

		int numSplinePoints = m_points.size();
		m_spline.resize(numSplinePoints);
		for (int pnt = 0; pnt < numSplinePoints; pnt++)
		{
			m_spline.key(pnt).flags = 0;
			m_spline.time(pnt) = (float)pnt/(numSplinePoints-1);
			m_spline.value(pnt) = m_points[pnt];
		}

		int nLengthSamples = m_params.nNumSegments+1;
		if (!(m_params.nFlags & IRopeRenderNode::eRope_Smooth))
			nLengthSamples = m_params.nPhysSegments+1;

		Vec3 p0 = m_points[0];
		Vec3 p1;

		for(i=1; i < nLengthSamples; i++) 
		{
			m_spline.interpolate( (float)i/(nLengthSamples-1),p1 );
			caps.axis = p1 - p0;
			caps.axis /= (caps.hh = caps.axis.len());	caps.hh *= 0.5f;
			caps.center = (p1 + p0)*0.5f - m_points[0];
			IGeometry *pGeom = pGeoman->CreatePrimitive(primitives::capsule::type, &caps);
			pgeom = pGeoman->RegisterGeometry(pGeom, surfaceTypesId[0]);
			pGeom->Release();
			pgeom->nRefCount = 0;	// we want it to be deleted together with the physical entity
			m_pPhysicalEntity->AddGeometry(pgeom, &gp);
			p0 = p1;
		}
	}

	m_bModified = true;
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::LinkEndPoints()
{
	if (m_pPhysicalEntity)
	{
		if (m_params.mass > 0)
		{
			pe_params_flags par_flags;
			par_flags.flagsOR = pef_disabled;
			m_pPhysicalEntity->SetParams(&par_flags); // To prevent rope from awakining objects on linking.
		}

		pe_params_rope pr;

		pr.pEntTiedTo[0] = 0;
		pr.pEntTiedTo[1] = 0;
		AnchorEndPoints( pr );
		m_pPhysicalEntity->SetParams(&pr);

		if (m_params.mass > 0)
		{
			pe_params_flags par_flags;
			par_flags.flagsAND = ~pef_disabled;
			m_pPhysicalEntity->SetParams(&par_flags);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::LinkEndEntities(IPhysicalEntity* pStartEntity, IPhysicalEntity* pEndEntity)
{
	m_nLinkedEndsMask = 0;

	if (!m_pPhysicalEntity || m_params.mass <= 0)
		return;

	if (m_params.mass > 0)
	{
		pe_params_flags par_flags;
		par_flags.flagsOR = pef_disabled;
		m_pPhysicalEntity->SetParams(&par_flags); // To prevent rope from awakining objects on linking.
	}

	pe_params_rope pr;

	if (pStartEntity)
	{
		pr.pEntTiedTo[0] = pStartEntity;
		pr.idPartTiedTo[0] = 0;
		m_nLinkedEndsMask |= 0x01;
	}

	if (pEndEntity)
	{
		pr.pEntTiedTo[1] = pEndEntity;
		pr.idPartTiedTo[1] = 0;
		m_nLinkedEndsMask |= 0x02;
	}

	m_pPhysicalEntity->SetParams(&pr);

	if (m_params.mass > 0)
	{
		pe_params_flags par_flags;
		par_flags.flagsAND = ~pef_disabled;
		m_pPhysicalEntity->SetParams(&par_flags);
	}
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::GetEndPointLinks( SEndPointLink *links )
{
	links[0].pPhysicalEntity = 0;
	links[0].offset.Set(0,0,0);
	links[0].nPartId = 0;
	links[1].pPhysicalEntity = 0;
	links[1].offset.Set(0,0,0);
	links[1].nPartId = 0;
	
	pe_params_rope pr;
	if (m_pPhysicalEntity)
	{
		m_pPhysicalEntity->GetParams( &pr );
		for (int i = 0; i < 2; i++)
		{
			if (pr.pEntTiedTo[i] && (UINT_PTR)pr.pEntTiedTo[i] != ~0)
			{
				links[i].pPhysicalEntity = pr.pEntTiedTo[i];
				links[i].nPartId = pr.idPartTiedTo[i];

				Matrix34 tm;
				pe_status_pos ppos;
				ppos.pMtx3x4 = &tm;
				pr.pEntTiedTo[i]->GetStatus(&ppos);
				tm.Invert();
				links[i].offset = tm.TransformPoint(pr.ptTiedTo[i]); // To local space.
			}
			else
				links[i].offset = Vec3(0,0,0);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::AnchorEndPoints( pe_params_rope &pr )
{
	m_nLinkedEndsMask = 0;
	//m_params.fAnchorRadius
	if (!m_pPhysicalEntity || m_params.mass <= 0)
		return;

	if (m_points.size() < 2)
		return;

	//pr.pEntTiedTo[0] = WORLD_ENTITY;
	//pr.pEntTiedTo[1] = WORLD_ENTITY;
	bool bEndsAdjusted=false,bAdjustEnds=m_params.fAnchorRadius>0.05001f;
	Vec3 ptend[2] = { m_worldTM*m_points[0], m_worldTM*m_points[m_points.size()-1] };

	primitives::sphere sphPrim;

	geom_contact *pContacts = 0;
	sphPrim.center = m_worldTM.TransformPoint(m_points[0]);
	sphPrim.r = m_params.fAnchorRadius;

	int collisionEntityTypes = ent_static|ent_sleeping_rigid|ent_rigid|ent_ignore_noncolliding;
	float d = 0;
	d = gEnv->pPhysicalWorld->PrimitiveWorldIntersection( sphPrim.type, &sphPrim, Vec3(0,0,0),collisionEntityTypes, &pContacts,0, geom_colltype0, 0);
	if (d > 0 && pContacts)
	{
		IPhysicalEntity *pBindToEntity = gEnv->pPhysicalWorld->GetPhysicalEntityById(pContacts[0].iPrim[0]);
		if (pBindToEntity)
		{
			pr.pEntTiedTo[0] = pBindToEntity;
			pr.idPartTiedTo[0] = pContacts[0].iPrim[1];
			m_nLinkedEndsMask |= 0x01;
			if (bAdjustEnds && inrange((pContacts->center-sphPrim.center).len2(),sqr(0.01f),sqr(m_params.fAnchorRadius)))
				ptend[0]=pr.ptTiedTo[0] = pContacts->t>0 ? pContacts->pt:pContacts->center, bEndsAdjusted=true;
		}
	}

	sphPrim.center = m_worldTM.TransformPoint(m_points[m_points.size()-1]);
	d = gEnv->pPhysicalWorld->PrimitiveWorldIntersection( sphPrim.type, &sphPrim, Vec3(0,0,0),collisionEntityTypes, &pContacts,0, geom_colltype0, 0);
	if (d > 0 && pContacts)
	{
		IPhysicalEntity *pBindToEntity = gEnv->pPhysicalWorld->GetPhysicalEntityById(pContacts[0].iPrim[0]);
		if (pBindToEntity)
		{
			pr.pEntTiedTo[1] = pBindToEntity;
			pr.idPartTiedTo[1] = pContacts[0].iPrim[1];
			m_nLinkedEndsMask |= 0x02;
			if (bAdjustEnds && inrange((pContacts->center-sphPrim.center).len2(),sqr(0.01f),sqr(m_params.fAnchorRadius)))
				ptend[1]=pr.ptTiedTo[1] = pContacts->t>0 ? pContacts->pt:pContacts->center, bEndsAdjusted=true;
		}
	}

	if (bEndsAdjusted && !is_unused(pr.length) && 
			inrange((pr.pPoints[0]-pr.pPoints[pr.nSegments]).len2(), sqr(pr.length*0.999f),sqr(pr.length*1.001f)))
		pr.length = (ptend[0]-ptend[1]).len();
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::Dephysicalize(bool bKeepIfReferenced)
{
	// delete old physics
	if (m_pPhysicalEntity)
		gEnv->pPhysicalWorld->DestroyPhysicalEntity(m_pPhysicalEntity);
	m_pPhysicalEntity = 0;
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::SetMaterial( IMaterial* pMat ) 
{ 
	if (!pMat)
		pMat = m_pMaterial = GetMatMan()->GetDefaultMaterial();
	m_pMaterial = pMat;
}

//////////////////////////////////////////////////////////////////////////
IMaterial* CRopeRenderNode::GetMaterial( Vec3 * pHitPos ) 
{ 
	return m_pMaterial;
}

//////////////////////////////////////////////////////////////////////////
float CRopeRenderNode::GetMaxViewDist()
{
	if (GetMinSpecFromRenderNodeFlags(m_dwRndFlags) == CONFIG_DETAIL_SPEC)
		return max(GetCVars()->e_ViewDistMin,GetBBox().GetRadius()*GetCVars()->e_ViewDistRatioDetail*GetViewDistRatioNormilized());

	return( max( GetCVars()->e_ViewDistMin,GetBBox().GetRadius()*GetCVars()->e_ViewDistRatio * GetViewDistRatioNormilized() ) );
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::Precache()
{

}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::GetMemoryUsage( ICrySizer* pSizer ) const
{
	SIZER_COMPONENT_NAME( pSizer, "RopeRenderNode" );
	pSizer->AddObject( this, sizeof( *this ) );
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::SyncWithPhysicalRope( bool bForce )
{
	if (m_bStaticPhysics)
	{
		m_physicsPoints = m_points;
		int numPoints = m_points.size();
		for (int i = 0; i < numPoints; i++)
		{
			Vec3 point = m_points[i];
			m_localBounds.Add(point);
			point = m_worldTM.TransformPoint(point);
			m_WSBBox.Add(point);
		}
		float r = m_params.fThickness;
		m_localBounds.min -= Vec3(r,r,r);
		m_localBounds.max += Vec3(r,r,r);
		m_WSBBox.min -= Vec3(r,r,r);
		m_WSBBox.max += Vec3(r,r,r);
		m_bNeedToReRegister = true; // Bounding box was recalculated, rope needs to be registered again 3d engine.
		m_bModified = false;
		return;
	}
	//////////////////////////////////////////////////////////////////////////
	if (m_pPhysicalEntity)
	{
		pe_status_rope sr;
		if (!m_pPhysicalEntity->GetStatus(&sr))
			return;
		int numPoints = sr.nSegments+1;
		if (m_params.nFlags&eRope_Subdivide && sr.nVtx != 0)
			numPoints = sr.nVtx;
		if (numPoints < 2)
			return;
		m_physicsPoints.resize(numPoints);
		if (m_physicsPoints.empty())
		{
			return;
		}
		m_WSBBox.Reset();
		m_localBounds.Reset();
		if (m_params.nFlags&eRope_Subdivide && sr.nVtx != 0)
			sr.pVtx = &m_physicsPoints[0];
		else
			sr.pPoints = &m_physicsPoints[0];
		m_pPhysicalEntity->GetStatus(&sr);
		for (int i = 0; i < numPoints; i++)
		{
			Vec3 point = m_physicsPoints[i];
			m_WSBBox.Add(point);
			point = m_InvWorldTM.TransformPoint(point);
			m_physicsPoints[i] = point;
			m_localBounds.Add(point);
		}
		float r = m_params.fThickness;
		m_localBounds.min -= Vec3(r,r,r);
		m_localBounds.max += Vec3(r,r,r);
		m_WSBBox.min -= Vec3(r,r,r);
		m_WSBBox.max += Vec3(r,r,r);
		m_bNeedToReRegister = true; // Bounding box was recalculated, rope needs to be registered again 3d engine.
	}
	//////////////////////////////////////////////////////////////////////////
	m_bModified = false;
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::CreateRenderMesh()
{
	// make new RenderMesh
	SAFE_RELEASE(m_pRenderMesh);

	//////////////////////////////////////////////////////////////////////////
	m_pRenderMesh = GetRenderer()->CreateRenderMeshInitialized(
		NULL, 0, eVF_P3F_C4B_T2F,
		NULL, 0, prtTriangleList,
		"Rope",GetName(),
		eRMT_Static, 1, 0, NULL, NULL, false, false );

	CRenderChunk chunk;
	memset( &chunk,0,sizeof(chunk) );
	if (m_pMaterial)
		chunk.m_nMatFlags = m_pMaterial->GetFlags();
	chunk.nFirstIndexId = 0;
	chunk.nFirstVertId = 0;
	chunk.nNumIndices = 3;
	chunk.nNumVerts = 3;
	chunk.m_texelAreaDensity = 1.0f;
	m_pRenderMesh->SetChunk( 0,chunk );
  if (m_pRenderMesh->GetChunks()[0].pRE)
    m_pRenderMesh->GetChunks()[0].pRE->mfUpdateFlags(FCEF_UPDATEALWAYS);
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::UpdateRenderMesh()
{
	FUNCTION_PROFILER_3DENGINE;

	if (!m_pRenderMesh)
	{
		CreateRenderMesh();
		if (!m_pRenderMesh)
			return;
	}

	if (m_physicsPoints.size() < 2 && !m_bStaticPhysics)
		return;
	if (m_points.size() < 2)
		return;

	SyncWithPhysicalRope(false);

	//////////////////////////////////////////////////////////////////////////
	// Tesselate.  
	//////////////////////////////////////////////////////////////////////////
	int numSplinePoints = m_physicsPoints.size();
	m_spline.resize(numSplinePoints);
	for (int pnt = 0; pnt < numSplinePoints; pnt++)
	{
		m_spline.key(pnt).flags = 0;
		m_spline.time(pnt) = pnt;
		m_spline.value(pnt) = m_physicsPoints[pnt];
	}
	m_spline.SetRange( 0,m_physicsPoints.size()-1 );

	int nLengthSamples = m_params.nNumSegments+1;
	if (!(m_params.nFlags & IRopeRenderNode::eRope_Smooth))
		nLengthSamples = m_params.nPhysSegments+1;

	Vec2 vTexMin(0,0);
	Vec2 vTexMax(m_params.fTextureTileU,m_params.fTextureTileV);

	CRopeSurfaceCache::GetLocalCache getCache;
	TubeSurface &tubeSurf = getCache.pCache->tubeSurface;

	tubeSurf.m_worldTM = m_worldTM;
	//tubeSurf.m_worldTM.SetIdentity();

	tubeSurf.GenerateSurface( &m_spline,m_params.fThickness,false,Vec3(0,0,1.0f),
		nLengthSamples,m_params.nNumSides,true,false,false,false,&vTexMin,&vTexMax );

	int nNewVertexCount = tubeSurf.iVQuantity;
	int nPrevVertexCount = m_pRenderMesh->GetVerticesCount();

	// Resize vertex buffer.
	m_pRenderMesh->LockForThreadAccess();
	m_pRenderMesh->UpdateVertices(NULL, nNewVertexCount, 0, VSF_GENERAL, 0u);
	
	int nPosStride = 0;
	int nUVStride = 0;
	int nTangetStride = 0;
	int nBinormalStride = 0;

	byte* pVertPos = m_pRenderMesh->GetPosPtr(nPosStride, FSL_VIDEO_CREATE);
  if (!pVertPos)
		return;
	byte* pVertTexUV = m_pRenderMesh->GetUVPtr(nUVStride, FSL_VIDEO_CREATE);

	byte* pTangents = m_pRenderMesh->GetTangentPtr(nTangetStride, FSL_VIDEO_CREATE);
	byte* pBinormals = m_pRenderMesh->GetBinormalPtr(nBinormalStride, FSL_VIDEO_CREATE);

	for (int i = 0; i < nNewVertexCount; i++)
	{
    Vec3 vP = Vec3(tubeSurf.m_akVertex[i].x, tubeSurf.m_akVertex[i].y, tubeSurf.m_akVertex[i].z);
    Vec2 vT = Vec2(tubeSurf.m_akTexture[i].x, tubeSurf.m_akTexture[i].y);
		*((Vec3*)pVertPos) = vP;
		*((Vec2*)pVertTexUV) = vT;

		Vec3 *Tangent = &tubeSurf.m_akTangents[i];
		Vec3 *Binormal = &tubeSurf.m_akBinormals[i];
		
		//*((Vec4sf*)pTangents) = Vec4sf(tPackF2B(Tangent->x),tPackF2B(Tangent->y),tPackF2B(Tangent->z), Tangent->w);
		*((Vec4sf*)pTangents) = Vec4sf(tPackF2B(Tangent->x),tPackF2B(Tangent->y),tPackF2B(Tangent->z), tPackF2B(1));
		*((Vec4sf*)pBinormals) = Vec4sf(tPackF2B(Binormal->x),tPackF2B(Binormal->y),tPackF2B(Binormal->z), tPackF2B(1));

		pVertPos += nPosStride;
		pVertTexUV += nUVStride;
		pTangents += nTangetStride;
		pBinormals += nBinormalStride;
	}

  m_pRenderMesh->UnlockStream(VSF_GENERAL);
  m_pRenderMesh->UnlockStream(VSF_TANGENTS);
	m_pRenderMesh->UpdateIndices(tubeSurf.m_pIndices, tubeSurf.iNumIndices, 0, 0u);

	// Update chunk params.
	CRenderChunk *pChunk = &m_pRenderMesh->GetChunks()[0];
	if (m_pMaterial)
		pChunk->m_nMatFlags = m_pMaterial->GetFlags();
	pChunk->nNumIndices = tubeSurf.iNumIndices;
	pChunk->nNumVerts = tubeSurf.iVQuantity;
  m_pRenderMesh->SetChunk(0, *pChunk);

	m_pRenderMesh->UnLockForThreadAccess();
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::SetParams( const SRopeParams& params )
{
	m_params = params;
}

//////////////////////////////////////////////////////////////////////////
const CRopeRenderNode::SRopeParams& CRopeRenderNode::GetParams() const
{
	return m_params;
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::SetPoints( const Vec3 *pPoints,int nCount )
{
	m_points.resize(nCount);
	m_physicsPoints.resize(nCount);
	if (nCount > 0)
	{
		m_localBounds.Reset();
		for (int i = 0; i < nCount; i++)
		{
			m_points[i] = pPoints[i];
			m_physicsPoints[i] = pPoints[i];
			m_localBounds.Add(pPoints[i]);
		}
	}
	float r = m_params.fThickness;
	m_localBounds.min -= Vec3(r,r,r);
	m_localBounds.max += Vec3(r,r,r);
	m_WSBBox.SetTransformedAABB( m_worldTM,m_localBounds );

	Get3DEngine()->RegisterEntity( this );
	m_bNeedToReRegister = false;

	Physicalize();
	m_bModified = true;

	if (m_pPhysicalEntity)
	{
		// Awake on first render after modification.
		//pe_action_awake pa;
		//pa.bAwake = 1;
		//m_pPhysicalEntity->Action( &pa );
	}
}

//////////////////////////////////////////////////////////////////////////
int CRopeRenderNode::GetPointsCount() const
{
	return (int)m_points.size();
}

//////////////////////////////////////////////////////////////////////////
const Vec3* CRopeRenderNode::GetPoints() const
{
	if (!m_points.empty())
		return &m_points[0];
	else
		return 0;
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::ResetPoints()
{
	m_physicsPoints = m_points;

	Physicalize();
	m_bModified = true;
}

void CRopeRenderNode::OnPhysicsPostStep()
{
	// Re-register entity.
	if (m_bNeedToReRegister)
		Get3DEngine()->RegisterEntity(this);
	m_bNeedToReRegister = false;
	m_bModified = true;

	// Update the sound data
	UpdateSound();
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::UpdateSound()
{
	if (m_ropeSoundData.nSoundID != INVALID_SOUNDID)
	{
		_smart_ptr<ISound> const pSound = gEnv->pSoundSystem->GetSound( m_ropeSoundData.nSoundID );
		if (pSound)
		{
			// Rope is moving, therefore update the sound now
			// Get rope data
			pe_params_rope oRopeParams;
			m_pPhysicalEntity->GetParams( &oRopeParams );

			int nIndexToUse = m_ropeSoundData.nSegementToAttachTo-1;
			Vec3 v3SoundPos = oRopeParams.pPoints[nIndexToUse];

			// Calculate an offset if set
			if (m_ropeSoundData.fOffset > 0.0f)
			{
				if (m_ropeSoundData.fOffset == 1.0f)
				{
					nIndexToUse	= m_ropeSoundData.nSegementToAttachTo;
					v3SoundPos	= oRopeParams.pPoints[nIndexToUse];
				}
				else
				{
					if (m_ropeSoundData.fOffset > 0.5f)
						nIndexToUse	= m_ropeSoundData.nSegementToAttachTo;

					v3SoundPos += (oRopeParams.pPoints[m_ropeSoundData.nSegementToAttachTo] - oRopeParams.pPoints[m_ropeSoundData.nSegementToAttachTo - 1]) * m_ropeSoundData.fOffset;
				}
			}

			// Calculate the angle between the rope segments we're attached to
			if (nIndexToUse != 0 && nIndexToUse != oRopeParams.nSegments)
			{
				// Do this only if we're not attached to either end
				Vec3 v3Segment1 = oRopeParams.pPoints[nIndexToUse] - oRopeParams.pPoints[nIndexToUse - 1];
				Vec3 v3Segment2 = oRopeParams.pPoints[nIndexToUse] - oRopeParams.pPoints[nIndexToUse + 1];
				v3Segment1.Normalize();
				v3Segment2.Normalize();

				float const fAngle = RAD2DEG( acosf( max( -1.0f, v3Segment1.Dot( v3Segment2 ) ) ) );

				// Update the angle parameter (min 0 max 180 in degree)
				pSound->SetParam( "angle", fAngle );
			}

			// Update position and velocity
			pSound->SetPosition( v3SoundPos );
			pSound->GetInterfaceExtended()->SetVelocity( oRopeParams.pVelocities[nIndexToUse] );
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::SetRopeSound( char const* const pcSoundName, int unsigned const nSegmentToAttachTo, float const fOffset )
{
	m_ropeSoundData.nSegementToAttachTo	= nSegmentToAttachTo;
	m_ropeSoundData.fOffset							= fOffset;

	if (pcSoundName && pcSoundName[0])
	{
		bool bAlreadyExists = false;

		// Stop a potentially already existing sound
		if (m_ropeSoundData.nSoundID != INVALID_SOUNDID)
		{
			_smart_ptr<ISound> const pOldSound = gEnv->pSoundSystem->GetSound( m_ropeSoundData.nSoundID );
			if (pOldSound)
			{
				// Make sure we don't create a new sound if we already loaded the passed one
				char const* const pcName = pOldSound->GetName();
				if (pcName!=0 && _stricmp(pcName, pcSoundName))
					StopRopeSound();
				else
				{
					bAlreadyExists = true;
					ResetRopeSound();
				}
			}
		}

		if (!bAlreadyExists)
		{
			// Now create the new one
			int const nSoundFlags = FLAG_SOUND_DEFAULT_3D|FLAG_SOUND_EVENT|FLAG_SOUND_LOAD_SYNCHRONOUSLY;
			_smart_ptr<ISound> const pNewSound = gEnv->pSoundSystem->CreateSound( pcSoundName, nSoundFlags );
			if (pNewSound)
			{
				m_ropeSoundData.nSoundID = pNewSound->GetId();
				pNewSound->SetSemantic( eSoundSemantic_Physics_General );
				ResetRopeSound();
				pNewSound->Play();
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::StopRopeSound()
{
	if (m_ropeSoundData.nSoundID != INVALID_SOUNDID)
	{
		_smart_ptr<ISound> const pSound = gEnv->pSoundSystem->GetSound( m_ropeSoundData.nSoundID );
		if (pSound)
			pSound->Stop();

		m_ropeSoundData.nSoundID = INVALID_SOUNDID;
	}
}

//////////////////////////////////////////////////////////////////////////
void CRopeRenderNode::ResetRopeSound()
{
	if (m_ropeSoundData.nSoundID != INVALID_SOUNDID)
	{
		_smart_ptr<ISound> const pSound = gEnv->pSoundSystem->GetSound( m_ropeSoundData.nSoundID );
		if (pSound)
		{
			// Get rope data
			pe_params_rope oRopeParams;
			m_pPhysicalEntity->GetParams( &oRopeParams );

			int nIndexToUse = m_ropeSoundData.nSegementToAttachTo-1;
			Vec3 v3SoundPos = oRopeParams.pPoints[nIndexToUse];

			// Calculate an offset if set
			if (m_ropeSoundData.fOffset > 0.0f)
			{
				if (m_ropeSoundData.fOffset == 1.0f)
				{
					nIndexToUse	= m_ropeSoundData.nSegementToAttachTo;
					v3SoundPos	= oRopeParams.pPoints[nIndexToUse];
				}
				else
				{
					if (m_ropeSoundData.fOffset > 0.5f)
						nIndexToUse	= m_ropeSoundData.nSegementToAttachTo;

					v3SoundPos += (oRopeParams.pPoints[m_ropeSoundData.nSegementToAttachTo] - oRopeParams.pPoints[m_ropeSoundData.nSegementToAttachTo - 1]) * m_ropeSoundData.fOffset;
				}
			}

			// Update position and velocity
			pSound->SetPosition( v3SoundPos );
			pSound->GetInterfaceExtended()->SetVelocity( Vec3(0.0f, 0.0f, 0.0f) );
			pSound->SetParam( "angle", 180.0f );
		}
	}

}

void CRopeRenderNode::OffsetPosition(const Vec3& delta)
{
#ifdef SEG_WORLD
	if (m_pRNTmpData) m_pRNTmpData->OffsetPosition(delta);
	m_pos += delta;
	m_worldTM.SetTranslation(delta);
	m_InvWorldTM = m_worldTM.GetInverted();
	m_WSBBox.Move(delta);
#endif
}

#include UNIQUE_VIRTUAL_WRAPPER(IRopeRenderNode)
